/*-
 * Copyright (c) 2007-2008 SINA Corporation, All Rights Reserved.
 * Author: Zhang shuo <zhangshuo@staff.sina.com.cn>
 * A http protocol implement under Zhu Yan's SASE(csf) framework.
 */

#include <sys/types.h>
#include <stdio.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <time.h>

#include "http.h"
#include "log.h"

#define REQUEST_LINE_KEYS   3   /* the number of key fields of request line */

static void parse_req_line(char *req_ptr, char **save_back, int len);
static int read_line(char *str, HEADER_FIELD *header_field_ptr, char **req_tail);

/* 
 * parse the request line -> ( GET /tc/fm/ HTTP/1.1 )
 * req_ptr is the string to parse. REQUEST_LINE_KEYS is the key count.
 * the returned string's pointer is saved in save_back[0],[1],[2]
 */
static void
parse_req_line(char *req_ptr, char **save_back, int len)
{
    int i = 0;
    save_back[0] = req_ptr;
    
    while(*req_ptr != '\0')
    {
        if (*req_ptr == ' ' || *req_ptr == '\r')
        {
            *req_ptr = '\0';
            i++;
            if (i == len)
            {
                break;
                return;
            }
            req_ptr++;
            save_back[i] = req_ptr;
        }
        req_ptr++;
    }
    return;
}


/* 
 * return each line's head pointer.
 * if an empty was read, a NULL will be returned 
 * str : string  line : start line  
 * req_tail : the tail address of request field or thr head of entity field
 *            a save back parameter
 */
static int
read_line(char *str, HEADER_FIELD *header_field_ptr, char **req_tail)
{
    char *line_head;        /* the positon of line head */
    int count = 0;
    char spliter[] = ":";   /* spliter between key & value */
    char *colon_pos;        /* the positon of ':' */
    char *option;           /* parsed value */
    
    line_head = str;
    while (*str != '\0')
    {
        if (*str == '\r')
        {
            *str = '\0';
            if (*(++str) == '\n')
            {
                *str = '\0';
            }
            
            if (strlen(line_head) == 0)
            {
                #ifdef DEBUG
                printf("line end\n");
                #endif
                *req_tail = str;
                return 0;
            }
            #ifdef DEBUG
            printf("line %d:%s\n", count, line_head);
            #endif
            /* search for the ':' */
            colon_pos = strstr(line_head, spliter);
            if (colon_pos == NULL)
            {
                #ifdef LOGGING
                WLOG_ERR("[HTTP] ':' not found in header. parsing failed.");
                #endif
                return -1;
            }
            /* split the string with '\0' */
            *colon_pos = '\0';
            option = colon_pos + 1;

            /* save the option string pointer in header_field_ptr */
            set_header_field_value(header_field_ptr, trim(line_head), option);

            count++;
            line_head = str + 1;
        }
        str++;
    }
    return 0;
}


/* 
 * this function parse the http request string.
 * request line: GET /sina.com/ HTTP/1.0  -> method, uri, protocol
 * and the request header (key -> value) are recognised.
 */

int 
parse_http_request(HTTP_REQUEST *req_info)
{
    char *request_line_ptr[REQUEST_LINE_KEYS] = {NULL};  /* parsed request poninter */
    char *req_tail = NULL;     /* indicates the tail of request header fields */
    char *entity_start; /* indicates the start of request entity fields */
    ssize_t entity_length;

    /* parse the request line, save the string's poninter back in temp_pos */
    parse_req_line(req_info->request_data, (char **)&request_line_ptr, REQUEST_LINE_KEYS);
    
    if (request_line_ptr[0] == NULL 
        || request_line_ptr[1] == NULL 
        || request_line_ptr[2] == NULL )
    {
        #ifdef LOGGING
        WLOG_ERR("[HTTP] request line parsing fails. http request invalidate.");
        #endif
        return -1;
    }

    /* save the method, uri, protocol's pointer to array */
    urldecode(request_line_ptr[1]);
    req_info->header_field_ptr[METHOD].field_value = request_line_ptr[0];   /* method */
    req_info->header_field_ptr[URI].field_value = request_line_ptr[1];      /* uri */
    req_info->header_field_ptr[PROTOCOL].field_value = request_line_ptr[2]; /* protocol */
    
    /* skip the request line to the start of request header fields */
    while(*req_info->request_data != '\n')req_info->request_data++;
    req_info->request_data++;   
    
    #ifdef DEBUG
    printf("method: %s\n", request_line_ptr[0]); 
    printf("uri: %s\n", request_line_ptr[1]); 
    printf("protocol: %s\n", request_line_ptr[2]);
    printf("\nheader_field=\n%s\n", req_info->request_data);
    #endif
    
    /* process every line of header and save in header_field_ptr */
    if (read_line(req_info->request_data, req_info->header_field_ptr, &req_tail) == -1)
    {
        return -1;
    }
    
    /* if the method = "POST", the entity field exists(post message) */
    if (strncmp(request_line_ptr[0], "P", 1) == 0)
    {
        /* parse the entity field which starts behind \r\n\r\n */
        entity_start = req_tail + 1;
        
        if (req_info->header_field_ptr[CONTENT_LENGTH].field_value != NULL)
        {
            /* max post length limit */
            entity_length = (ssize_t)atol(req_info->header_field_ptr[CONTENT_LENGTH].field_value);
            
            if (entity_length > MAX_POST_BYTES)
            {
                #ifdef LOGGING
                WLOG_ERR("[HTTP] content length over limit.");
                #endif
            }
            else
            {
                req_info->request_entity_len = entity_length;
            }
        }
        else
        {
            return -1;
            #ifdef LOGGING
            WLOG_ERR("[HTTP] no content length specified when post.");
            #endif
        }
    }

    /* 
     * now the req_info is set successfully .
     * function will return, and the http_entry will be end.
     * there will be a submit to pipeline later
     */
    
    return 0;
}




